﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using SdmxMl.Common;
using SdmxMl.Structure;
using SdmxMl.Multiplexor;
using SdmxMl.GenericMetadata;

namespace SdmxMl.Manager
{
    /// <summary>
    /// Mode for getting Qrtefact and it's children
    /// </summary>
    public enum DescendantMode
    {
        Single,
        Children,
        Descendants
    }

    /// <summary> Publishes the method definition in the form of a delegate </summary>
    /// <param name="sender"></param>/ <param name="e"> instance of ArtefactRefEventArgs</param>
    public delegate void ArtefactToManageEvent(object sender, ArtefactRefEventArgs e);


    /// <summary>
    /// This class manages retrival of all descendant of an artefact (or a list of artefacts)
    /// It checks also a defined size limit, and allows proxy info support for some REST cases
    /// The case of aggregated documents should be avoided as possible.
    /// </summary>
    public class DescendantMgr
    {
        #region Internal Members

        private SmManager sm;
        private ProxyService reg;
        DescendantMode mode;

        /// <summary>Typed Key is fullId, content is artefact ref with internationalized name</summary>
        private Dictionary<string, ArtefactRefTyped> dicoList;

        private Dictionary<string, string> dicoUriConcept;

        #endregion

        #region Properties

        /// <summary>
        /// List of artefact not found at end of Explore processus
        /// </summary>
        public Dictionary<string, ArtefactRefTyped> CopyList { get; private set; }

        public bool ThrowExceptionNotFound { get; set; }

        #endregion

        public event ArtefactToManageEvent ArtefactToManageEventExposed;
        public event ArtefactToManageEvent ArtefactMissingExposed;

        public bool RaiseArtefactToManageEven(ArtefactRefTyped art)
        {
            ArtefactRefEventArgs evArg = new ArtefactRefEventArgs(art);
            ArtefactToManageEvent eventHandler = this.ArtefactToManageEventExposed;
            if (eventHandler != null)
                eventHandler(this, evArg);
            return !evArg.Cancel;
        }

        public void RaiseArtefactMissingEven(ArtefactRefTyped art)
        {
            ArtefactRefEventArgs evArg = new ArtefactRefEventArgs(art);
            ArtefactToManageEvent eventHandler = this.ArtefactMissingExposed;
            if (eventHandler != null)
                eventHandler(this, evArg);
        }

        #region Properties

        public SmManager SmContainer { get { return sm; } }

        #endregion

        #region C'tors
        public DescendantMgr(SmManager smMgr, ArtefactRefTyped ar, DescendantMode descMode)
        {
            Init();
            mode = descMode;
            dicoList.Add(ar.TypedId, ar);
            sm = smMgr;
        }

        public DescendantMgr(ArtefactRefTyped ar, DescendantMode descMode)
        {
            Init();
            mode = descMode;
            dicoList.Add(ar.TypedId, ar);
        }

        public DescendantMgr(ArtefactRefTypedList artl, DescendantMode descMode)
        {
            Init();
            mode = descMode;
            foreach (ArtefactRefTyped ar in artl)
                dicoList.Add(ar.TypedId, ar);
        }

        private void Init()
        {
            dicoList = new Dictionary<string, ArtefactRefTyped>(32);
            sm = new SmManager();
            mode = DescendantMode.Children;
        }

        #endregion

        #region Explore descendants

        public void Explore()
        {
            bool somemore = false;
            int descentLevel = 0;
            do
            {
                // Clone list
                Dictionary<string, ArtefactRefTyped> copyList =
                    AdjustListManageEvent();

                // Fetch missing and get references of children
                LoadfromList(copyList, descentLevel);

                copyList = AdjustListManageEvent();
                somemore = copyList.Count > 0 && MissInContainer(copyList);

                // as long as something new to scan
                ++descentLevel;
                if (descentLevel > 10 || (somemore == false && MissInContainer(copyList)))
                {
                    StringBuilder sb = new StringBuilder(128);
                    sb.Append("Sorry. Unable to resolve some of these references !").Append(Environment.NewLine);
                    sb.Append(Environment.NewLine);
                    foreach (ArtefactRefTyped art in copyList.Values)
                    {
                        if (sm.GetArtefact(art) == null)
                            sb.Append(art.Type.ToString()).Append(" - ").Append(art.FullIdent).Append(Environment.NewLine);
                    }
                    throw new ApplicationException(sb.ToString());
                }

            } while (somemore);

        }

        private Dictionary<string, ArtefactRefTyped> AdjustListManageEvent()
        {
            // Clone list
            Dictionary<string, ArtefactRefTyped> copyList =
                new Dictionary<string, ArtefactRefTyped>(dicoList);

            if (this.ArtefactToManageEventExposed != null)
            {
                foreach (KeyValuePair<string, ArtefactRefTyped> kvp in dicoList)
                    if (RaiseArtefactToManageEven(kvp.Value) == false)
                        copyList.Remove(kvp.Key);
            }

            return copyList;
        }


        private bool MissInContainer(Dictionary<string, ArtefactRefTyped> theList)
        {
            foreach (ArtefactRefTyped art in theList.Values)
            {
                if (art.AgencyId != "CH1_SILC")
                    sm.UpdateLoadedStatus(null);
                // if not yet examined
                if (sm.GetArtefact(art) == null && RaiseArtefactToManageEven(art) == true)
                    return true;
            }
            
            return false;
        }
        #endregion

        #region Private Methods

        /// <summary>
        /// Load artefact according to references listm add new child references to list
        /// </summary>
        /// <param name="theList">list of references to load</param>
        /// <param name="descentLevel">depth</param>
        /// <returns>True if some new action done</returns>
        private void LoadfromList(Dictionary<string, ArtefactRefTyped> theList, int descentLevel)
        {
            Artefact arScanned = null;
            List<string> listRemove = new List<string>();

            // Scan list if not in SM container, load and update references list
            foreach (ArtefactRefTyped art in theList.Values)
            {
                if (RaiseArtefactToManageEven(art))
                {
                    // if not yet examined
                    if ((arScanned = sm.GetArtefact(art)) == null)
                    {
                        // Load doc, can be a aggregated one so use temporary sm loader
                        reg = Mux.Instance.GetProxyForGroup(art.FullIdent);
                        XmlDocument xDoc = reg.GetArtefact(art.Type.ToString(), art.FullIdent);

                        SmManager smLoaded = new SmManager();
                        smLoaded.LoadSdmxDocument(null, true, xDoc);

                        // for all fetched artefacts
                        foreach (ArtefactManager am in smLoaded.ArtefactManagerDico.Values)
                        {
                            foreach (Artefact a in am.ArtefactList)
                            {
                                ArtefactRefTyped artLoaded = new ArtefactRefTyped(am.ArtefactType, a.FullIdent);
                                if (sm.GetArtefact(artLoaded) == null)
                                {
                                    // store
                                    sm.PutArtefact(am.ArtefactType, a);

                                    // sm.PutArtefactProxy (todo for REST service
                                    // ??? add urnvase and isExternal ?

                                    if (mode == DescendantMode.Descendants ||
                                        mode == DescendantMode.Children && descentLevel < 1)
                                    {
                                        // If artefact is a metadataset it cannot gives ref to Artefact refered
                                        // in reported value of URI type (and starting with sdmx urn base)
                                        MetadataSetType metaSet = a as MetadataSetType;
                                        if (metaSet != null)
                                            GetMetasetRef(metaSet);

                                        //For all: add unknown references of artefact in dico list
                                        ArtefactRefTypedList artl = a.ArtefactRefList;
                                        foreach (ArtefactRefTyped artRef in artl)
                                            if (sm.GetArtefact(artRef) == null &&
                                                dicoList.ContainsKey(artRef.TypedId) == false)
                                                    dicoList.Add(artRef.TypedId, artRef);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        if (mode == DescendantMode.Descendants ||
                            mode == DescendantMode.Children && descentLevel < 1)
                        {

                            // If artefact is a metadataset it cannot gives ref to Artefact refered
                            // in reported value of URI type (and starting with sdmx urn base)
                            MetadataSetType metaSet = arScanned as MetadataSetType;
                            if (metaSet != null)
                                GetMetasetRef(metaSet);

                            //For all: add unknown references of artefact in dico list
                            ArtefactRefTypedList artl = arScanned.ArtefactRefList;
                            foreach (ArtefactRefTyped artRef in artl)
                                if (sm.GetArtefact(artRef) == null &&
                                    dicoList.ContainsKey(artRef.TypedId) == false)
                                        dicoList.Add(artRef.TypedId, artRef);
                        }
                    }
                }
            }

        }

        #endregion

        #region MetaSet specific references

        private void GetMetasetRef(MetadataSetType metaSet)
        {
            dicoUriConcept = new Dictionary<string, string>();

            ArtefactRefTypedList artl = metaSet.ArtefactRefList;
            if (artl.Count > 1)
                throw new ApplicationException("Unexpected references count in metaset");

            else if (artl.Count == 1)
            {
                if (sm.GetArtefact(artl[0]) == null)
                {
                    if (artl[0].Type != SdmxArtefactType.MetadataStructureDefinitions)
                        throw new ApplicationException("Unexpected: metaset does not refers a MSD");

                    // Preload for determining if URI used in defined MSD Report 
                    reg = Mux.Instance.GetProxyForGroup(artl[0].FullIdent);
                    XmlDocument xDoc = reg.GetArtefact(artl[0].Type.ToString(), artl[0].FullIdent);

                    SmManager smLoaded = new SmManager();
                    smLoaded.LoadSdmxDocument(null, true, xDoc);
                    MetadataStructureDefinitionType msd = smLoaded.GetArtefact(artl[0]) as MetadataStructureDefinitionType;
                    if (msd == null)
                        throw new ArgumentException("Unable to obtain refered metadataStructure [" + artl[0].FullIdent + "]");

                    ReportStructureType report = msd.ReportStructureList.LocateItem(metaSet.ReportRef);
                    if (report == null)
                        throw new ArgumentException("Incorrect report reference [" + metaSet.ReportRef + "], it does not exist in associated MSD");

                    // Get Reportedattribute of type URI
                    IterateHierarchyMetaAttrib(msd, report, report.MetadataAttributeList);

                    // Now scan whole datas (if ans URI)
                    if (dicoUriConcept.Count > 0)
                    {
                        foreach (string conceptId in dicoUriConcept.Keys)
                        {
                            // For each meta row
                            foreach (AttributeValueSetType at in metaSet.AttributeValueSetList)
                            {
                                // Get URI value
                                ReportedAttributeType rptAtt = at.ReportedAttributeList.GetReportedAttributeOfConcept(conceptId);
                                string urn = rptAtt != null ? rptAtt.Value.GetFirst() : string.Empty;
                                ArtefactRefTyped art = null;
                                if (urn.StartsWith(UrnManager.UrnBase))
                                {
                                    UrnParser p = new UrnParser(urn);
                                    if (p.IsSupported)
                                    {
                                        art = new ArtefactRefTyped(p.ArType, p.ArtefactId, p.AgencyId, p.Version);

                                        // If not yet loaded or not already recorded
                                        // add unknown references of artefact in dico list
                                        if (sm.GetArtefact(art) == null &&
                                            dicoList.ContainsKey(art.TypedId) == false)
                                            dicoList.Add(art.TypedId, art);
                                    }
                                        
                                    
                                }
                            }

                        }

                    }
                }
            }

        }



        private void IterateHierarchyMetaAttrib(MetadataStructureDefinitionType msd, ReportStructureType rpt, MetadataAttributeTypeList metaList)
        {
            foreach (MetadataAttributeType meta in metaList)
            {
                // record case using URI in metadataset, for next domain search
                if (meta.TextFormat.TextType == TextTypeType.URI &&
                    meta.ConceptSchemeInfo.IsFilled && meta.ConceptInfo.Id.Length > 0)
                {
                    if (dicoUriConcept.ContainsKey(meta.ConceptInfo.Id) == false)
                        dicoUriConcept.Add(meta.ConceptInfo.Id, meta.ConceptSchemeInfo.FullIdent);
                }

                IterateHierarchyMetaAttrib(msd, rpt, meta.MetadataAttributeList);
            }
        }



        #endregion

    }
}
